# Система разграничения прав для GraphQL запросов

В **DataSpace** реализована возможность разграничения прав в разрезе GraphQL-запросов.
Разграничение прав определяется фиксацией специальных правил для каждого отдельного GraphQL-запроса.
Далее в документе данные правила будем называть "**разрешениями**".

## Общая схема работы

После выпуска сервиса **DataSpace** на вкладке "**Детали**" будет отображен ряд точек доступа (endpoints):
![img](../images/endpoints.png ':size=400')

* Точки доступа **packet** и **search** используются при работе с сервисом через Java SDK. 
* **dictionaries** - загрузка спровачникых данных
* **graphql** - доступ для Backend-приложений (автороризация посредством application key/secret key на API gateway)
* **graphql jwt** - доступ посредством [JWT](https://auth0.com/docs/secure/tokens/json-web-tokens) через систему разграничения прав.

**В рамках документа будут рассмотрены правила работы с разрешениями, проверка которы осуществляется через точку доступа graphql jwt**


Ниже представлена общая схема работы системы разграничения прав.

![img](../images/permission-scheme.png ':size=400')

## Категории пользователей (субъекты доступа)

Определены три категории потребителей сервиса DataSpace:

* **Администратор системы (Admin)** - пользователь, определяющий политики и правила безопасности в рамках системы разграничения прав.
  Основные задачи:
  * Управление внешней по отношению к DataSpace системой аутентификации и управления доступом, далее IAM.
      * Основные задачи администратора в рамках управления IAM-системой:
      * Управление пространствами (realms)
      * Управление приложениями-клиентами (clients)
      * Управления пользователями (users) и их ролями (roles)
      * Получение сертификатов (certs) для дальнейшей загрузки в системы DataSpace
      * Конфигурирование системы разграничения доступа на стороне DataSpace ( в режиме разработки/тестирования/отладки приложения):
      * Загрузка сертификатов (certs) для проверки корректности JWT
    **В данном докумениа правила настройки IAM не рассматриваются. Для работы можно воспользоваться одной из реализациейIAM [keycloack] (https://www.keycloak.org/getting-started/getting-started-zip**)
  * Формирование правил проверки GraphQL-запросов (объектов доступа) на основании содержимого JWT и бизнес-правил конкретной реализации системы DataSpace
* **Пользователь системы (User)** - потребитель основных функций DataSpace посредством GraphQL-запросов.
  Основные задачи:
  * Аутентификация в **IAM** для получения **JWT** и дальнейшего использования в обращении к **DataSpace**
  * Осуществление GraphQL-запросов к системе **DataSpace** с использованием **JWT**, полученного шагом ранее при успешной аутентификации в **IAM**
* **Backend-приложение (Backend App)**  - приложения, имеющие доступ к данным **DataSpace** на чтение/запись посредством GraphQL/Json RPC запросов на основе backend-авторизации (application key/secret key).

## Алгоритм проверки разрешений

**Алгоритм проверки GraphQL-запроса**:
* по имени входящего GraphQL-запроса определяется наличие соответствующего разрешения
* если разрешение найдено, сверяется на идентичность входящий запрос и запрос в разрешении
* если запросы идентичны, запускается проверка правил для найденного разрешения
  * проверяется необходимость проверки JWT по JWKS (при необходимости проверка по ранее загруженному в систему набору JWKS производится). По умолчанию соответствущий флаг disableJwtVerification не взведен.
  * проверяется необходимость явного наличия проверок (checkSelects) на разрешение выполнения запроса.  По умолчанию соответствущий флаг enableWithoutChecks не взведен.
  * если флаг enableWithoutChecks не взведен, последовательно выполняются все указанные для разрешения проверки
  * если все проверки пройдены, выполнение запроса разрешается, иначе выполнение запрещено
  * к переменным запроса, фильтрующим запрашиваемую выборку, добавляются обязательные доп.ограничения additionalParams, 

Ниже представлена **блок-схема алгоритма**:

![img](../images/perm_check_alg.png ':size=400')

## Администрирование разрешений

После выпуска сервиса **DataSpace** пользователю на вкладке "**Разрешения**" предоставлена возможность фиксации разрешений для отдельных GraphQL-запросов.

* Кнопки "**Импорт/Экспорт разрешений**" позволяют осуществить выгрузку/загрузку разрешений через файл.
* Кнопка "**Перезагрузить запросы**" позволяет осуществить перезагрузку тел запросов по их имени с сохранением ранее зафиксированых правил разрешений. Данная опция очень полезна в процессе разработки, когда тела запросов часто меняется и необходимо обновить соответствующие разрешения, сохранив ранее выставленные для них флаги и проверки.
* Кнопка "**Загрузить JWKS**" позволяет осуществить загрузку JWKS в формате JSON: https://auth0.com/docs/secure/tokens/json-web-tokens/json-web-key-set-properties 
* Кнопка "**Добавить разрешение**" открывает форму для создания нового разрешения, где для заполнения доступны следующие элементы: 
  * текстовое поле "**Запрос в формате graphQL**" - здесь фиксируется тело GraphQL-запроса, определяемого для создаваемого разрешения. Обратите внимание, что поле "**Наименование**" не доступно к заполнению. Оно формируется из самого тела запроса. Т.е. запрос обязательно должен быть именованнным.
  * Под телом запроса доступны поля, соотвествующие переменным, передающимся на вход в запрос. В данных полях имеется отразить дополнительные условия (additionalParam) фильтрации для запросов типа query. См. ниже "**Синтаксис условий проверок и дополнительных фильтраций**"
  * Еще ниже представлены два флага:
    * "**Разрешить без проверок**" - при взведении данного флага никакие дополнительные проверки для данного разрешения не будут производиться
    * "**Отключить проверку JWT**" - взвдение флага отключает проверку корректности JWT звапроса загрузхенным в систему JWKS
  * Если флаг "**Разрешить без проверок**" не взведен, то необходимо явно указать набор проверок (checkSelects), которые будут выполняться последовательно. После нажатия кнопки "+" к заполненю доступны следующие поля проверки:
    * "**Описание проверки**" - собственно, текстовое описание проверки
    * "**Условие**" - логика проверки. См. ниже "**Синтаксис условий проверок и дополнительных фильтраций**"
    * "**Тип**" - имя типа (сущности) модели данных, относительно который необходимо приментить зафиксированное условие. 

На скриншоте ниже представлен пример заполнения:
![img](../images/addPerm.png ':size=400')

GraphQL-запрос searchOrder доступен только пользователям с ролью **customer**. 
Об этом говорит соответствующая проверка с условием "**'customer' $in ${jwt:realm_access.roles}**".
За счет доп. условия фильтрации "**it.customer.entityId == ${jwt:email}**" обратно будут возвращены только заказы пользователя, совершившего запрос. 



## Синтаксис условий проверок и дополнительных фильтраций

Логика условий проверок (checkSelects) и доп. фильтраций (additionalParams) в **разрешениях** описывается той же грамматикой [строковых выражений](../graphql/string-expressions.md), что и фильтрация в поисковых GraphQL-запросах.
Доступ к содержимому JWT реализован посредством конструкции  **${jwt:...}** 

## Пример: "Система учета заказов"

Для простоты понимания правил и возможностей системы разграничения прав доступа рассмотрим функциональность на конкретном примере приложения.
Допустим, необходимо реализовать систему учета заказов определенных видов товаров пользователями.

Ниже представлена модель соответствующего сервиса DataSpace, зафиксирован граф статусов для заказов:
![img](../images/orderModel.png ':size=380')
![img](../images/orderStatus.png ':size=20')

[model.xml](./model.xml)

### Требования к ролевой модели проектируемой системы:

* Пользователь-менеджер (роль **manager** в IAM) имеет право:
  * создавать\изменять типы товаров (GoodType)
  * просматривать список товаров
  * просматривать полный список ВСЕХ заказов (Order) в статусе 'FIXED'

* Пользователь-клиент: (роль **customer** в IAM) имеет право:
  * просматривать список товаров
  * создавать заказ (Order)
  * наполнять заказ деталями (Detail), определяющими список товаров в рамках заказа (наполнение возможно только для "открытых" заказов: в статусе 'DRAFT')
  * у пользователя может быть только один "открытый" заказ: в статусе 'DRAFT'
  * удалять детали только СВОЕГО "открытого" заказа
  * просматривать список СВОИХ заказов любом статусе, в т.ч. его деталей
  * переводить СВОИ заказы в статус 'FIXED'
  * определять СВОИ персональные данные (CustomerPersonalData)

* Пользователь-супервайзер (роль **supervisor** в IAM) имеет право:
  * просматривать список товаров
  * просматривать полный список ВСЕХ заказов в любом статусе
  * просматривать персональные данные клиентов

### Необходимые GraphQL-запросы
Для реализации соответствующей логики нам понадобятся следующие GraphQL-запросы:

```
## Customer ###############################################
fragment CustomerAttributes on _E_Customer {
    id
    __typename
    data {
        name
        address
    }
}

query getCustomerInfo($cond: String!) {
  searchCustomer(cond: $cond) {
    elems {
      ...CustomerAttributes
    }
  }
}

mutation addCustomerInfo($customerInput: _CreateCustomerInput!) {
    packet {
        updateOrCreateCustomer(input: $customerInput) {
            returning {
                ...CustomerAttributes
            }
        }
    }
}

## GoodType ###############################################
fragment GoodTypeAttributes on _E_GoodType {
    id
    __typename
    name
    descr
    price
}

query searchGoodType {
  searchGoodType {
    elems {
      ...GoodTypeAttributes
    }
  }
}

mutation addGoodTypeInfo($goodTypeInput: _CreateGoodTypeInput!) {
    packet {
        updateOrCreateGoodType(input: $goodTypeInput) {
            returning {
                ...GoodTypeAttributes
            }
        }
    }
}

## Order & Detail #########################################
fragment DetailAttributes on _E_Detail {
    id
    __typename
    goodType {
        entity {
            ...GoodTypeAttributes
        }
    }
}

fragment OrderAttributes on _E_Order {
    id
    __typename
    openOrderFlag
    orderDate
    comment
    statusForCUSTOMER {
        code
    }
    details {
        elems {
            ...DetailAttributes
        }
    }
}

query searchOrder($cond: String) {
  searchOrder(cond: $cond, sort: {crit: "it.orderDate", order: DESC}) {
    elems {
      ...OrderAttributes
    }
  }
}

query searchAllOrder($cond: String) {
  searchOrder(cond: $cond, sort: {crit: "it.customer.entityId", order: DESC}) {
    elems {
      customer {
        entityId
      }
      ...OrderAttributes
    }
  }
}

mutation addOrderDetail($customerId: String!, $goodTypeId: String!) {
    packet {
        updateOrCreateOrder(
            input: {openOrderFlag: $customerId, customer: {entityId: $customerId}}
            exist: {byKey: openOrderFlag}
        ) {
            returning {
                ...OrderAttributes
            }
        }
        createDetail(
            input: {order: "ref:updateOrCreateOrder", goodType: {entityId: $goodTypeId}}
        ) {
            ...DetailAttributes
        }
    }
}

mutation fixOrder($orderId: ID!) {
  packet {
    updateOrder(
      input: {id: $orderId, openOrderFlag: null, statusForCUSTOMER: {code: "FIXED"}}
    ) {
      ...OrderAttributes
    }
  }
}

mutation deleteDetail($detailId: ID!) {
  packet {
    deleteDetail(id: $detailId)
  }
}
```
[requests.graphql](./requests.graphql)

## Разрешения

Ниже представлены **разрешения** для GraphQL-запросов, закрывающие изначальные требования:
![img](../images/orderPerm.png ':size=400')
[permissions.json](./permissions.json)


Разберем подробнее наиболее интересные из них:

**addCustomerInfo**:
Добавить/изменить информацию о пользователе может только сам пользователь, что обеспечивается соответствующей проверкой равенства передаваемого идентификатора **${customerInput.id}** соответствующему полю JWT: **${jwt:email}**.
Подразумевается, что в нашем примере в качестве идентификатора пользователя (Customer.id) будет использоваться его email.

**deleteDetail**:
Удалить деталь имеет право только сам собственник заказа и только если сам заказ находится в статусе 'DRAFT'.
Обратите внимание, что в данном ограничении также указана сущность для поиска: **Detail**, т.к. для проверки нам необходимо обратиться непосредственном к уже существующим данным в DataSpace:
* найти деталь, которую хочет удалить пользователь "**it.$id == ${detailId}**"
* проверить, что владельцем заказа является пользователь, инициировавший запрос: "**it.order.customer.entityId == ${jwt:email}**"
* убедиться, что найденный заказ в состоянии 'DRAFT': "**it.order.statusForCUSTOMER == 'DRAFT'**"

**searchAllOrder**:
Данный запрос используется для отображения всех заказов для пользователей с ролями **manager** и **supervisor**. В отличии от **supervisor** пользователи с ролью **manager** могут просматривать заказы только в состоянии 'FIXED'.
Данное ограничение реализуется слеудющим доп. условием фильтрации: **cond: 'supervisor' $in ${jwt:realm_access.roles} || ('manager' $in ${jwt:realm_access.roles} && it.statusForCUSTOMER.code == 'FIXED' )**

**searchGoodType**:Список всех товаров разрешен для просмотра всем пользователям. Взведен флаг "**Разрешить без проверок**" (AllowWithoutChecks)